using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using VoiceCommander.Configuration;
using System.Speech.Recognition;
using ManagedKeySimulator;
using System.Media;

namespace VoiceCommander
{
	public partial class frmKeySender : Form
	{
		List<String> _allCommands = new List<string>();
		CommandListType _originalConfiguration;
		
		CommandListType _currentConfiguration;
		Dictionary<String, KeyType[]> _currentActions;
		
		RecognizerInfo _recognizerInfo;
		SpeechRecognitionEngine _sre;
		SoundPlayer _computerSound = new SoundPlayer(Properties.Resources.ComputerSound);

		#region Special Commands
		private const string CancelCommand = "Cancel command";
		#endregion 

		private const int ConfidenceInitialValue = 80;

		public frmKeySender(CommandListType configuration, RecognizerInfo recognizerInfo)
		{
			InitializeComponent();

			tbConfidence.ValueChanged += new EventHandler(tbConfidence_ValueChanged);
			tbConfidence.Value = ConfidenceInitialValue;
			txtConfidence.Text = ConfidenceInitialValue.ToString();

			configuration.IsOriginal = true;
			_originalConfiguration = configuration;
			_recognizerInfo = recognizerInfo;

			_sre = new SpeechRecognitionEngine(_recognizerInfo);

			_sre.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(sre_SpeechRecognized);
			_sre.SetInputToDefaultAudioDevice();

			AddExtraCommands(configuration);
			LoadGrammars(configuration);
			SetCurrentActions(configuration);
			LoadSpecialCommands();
		}

		private void AddExtraCommands(CommandListType configuration)
		{
			CommandType[] newCommands = new CommandType[configuration.Command.Length + 1];
			for (int i = 0; i < configuration.Command.Length; i++)
			{
				newCommands[i] = configuration.Command[i];
			}

			int lastIndex = newCommands.Length - 1;
			newCommands[lastIndex] = new CommandType();
			newCommands[lastIndex].text = "Voice Commander";
			newCommands[lastIndex].InnerCommands = new CommandListType();
			newCommands[lastIndex].InnerCommands.Command = new CommandType[2];

			newCommands[lastIndex].InnerCommands.Command[0] = new CommandType();
			newCommands[lastIndex].InnerCommands.Command[0].text = "Stop";
			newCommands[lastIndex].InnerCommands.Command[0].Key = new KeyType[1];
			ExtendedKeyType ekStop = new ExtendedKeyType();
			ekStop.MethodCall = delegate()
			{
				chkEnabled.Checked = false;
			};

			newCommands[lastIndex].InnerCommands.Command[0].Key[0] = ekStop;

			newCommands[lastIndex].InnerCommands.Command[1] = new CommandType();
			newCommands[lastIndex].InnerCommands.Command[1].text = "Start";
			newCommands[lastIndex].InnerCommands.Command[1].Key = new KeyType[1];
			ExtendedKeyType ekStart = new ExtendedKeyType();
			ekStart.MethodCall = delegate()
			{
				chkEnabled.Checked = true;
			};

			newCommands[lastIndex].InnerCommands.Command[1].Key[0] = ekStart;

			configuration.Command = newCommands;
		}

		private void LoadGrammars(CommandListType commands)
		{
			List<String> phrases = new List<string>();
			foreach (CommandType command in commands.Command)
			{
				if (command.InnerCommands != null)
				{
					LoadGrammars(command.InnerCommands);
				}
				if (!_allCommands.Contains(command.text))
				{
					_allCommands.Add(command.text);
					phrases.Add(command.text);
					
				}
			}

			Choices choices = new Choices(phrases.ToArray());
			GrammarBuilder gb = choices.ToGrammarBuilder();
			gb.Culture = _sre.RecognizerInfo.Culture;
			_sre.LoadGrammar(new Grammar(gb));
		}

		private void SetCurrentActions(CommandListType commands)
		{
			_currentActions = new Dictionary<string, KeyType[]>();
			_currentConfiguration = commands;

			foreach (CommandType command in commands.Command)
			{
				_currentActions.Add(command.text, command.Key);
			}
		}

		private void LoadSpecialCommands()
		{
			Choices choices = new Choices(new string[] { 
				CancelCommand});
			GrammarBuilder gb = choices.ToGrammarBuilder();
			gb.Culture = _sre.RecognizerInfo.Culture;
			_sre.LoadGrammar(new Grammar(gb));			
		}
		
		private CommandType FindCommand(string commandText)
		{
			foreach (CommandType command in _currentConfiguration.Command)
			{
				if (command.text == commandText)
				{
					return command;
				}
			}

			return null;
		}

		#region Events
		void sre_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
		{
			int logLength = txtLog.Text.Length;
			double accuracy = (double)Convert.ToInt32((e.Result.Confidence * 10000)) / 100;

			if (accuracy >= tbConfidence.Value)
			{
				
				if (_currentActions.ContainsKey(e.Result.Text))
				{
					#region Execute command
					KeyType[] keys = _currentActions[e.Result.Text];

					txtLog.Text += String.Format("Command: {0} ({1}%)", e.Result.Text, accuracy);
					if (keys != null)
					{
						foreach (KeyType key in keys)
						{
							ExtendedKeyType eKey = key as ExtendedKeyType;
							if (eKey != null && eKey.MethodCall != null)
							{
								eKey.MethodCall();
							}
							else if (chkEnabled.Checked)
							{
								if (key.special != SpecialType.None)
								{
									key.code = SpecialKeyTranslator.Translate(key.special);
								}

								#region Key Action
								switch (key.action)
								{
									case ActionType.Press:
										if (key.@char != null)
										{
											Keyboard.PressKey(key.@char[0]);
											txtLog.Text += String.Format("\r\n\t- Pressed {0}", key.@char[0]);
										}
										else
										{
											Keyboard.PressKey((Byte)key.code);
											txtLog.Text += String.Format("\r\n\t- Pressed KeyCode {0}", key.code);
										}
										break;

									case ActionType.Hold:
										if (key.@char != null)
										{
											Keyboard.HoldKey(key.@char[0]);
											txtLog.Text += String.Format("\r\n\t- Holding {0}", key.@char[0]);
										}
										else
										{
											Keyboard.HoldKey((Byte)key.code);
											txtLog.Text += String.Format("\r\n\t- Holding KeyCode {0}", key.code);
										}
										break;

									case ActionType.Release:
										if (key.@char != null)
										{
											Keyboard.ReleaseKey(key.@char[0]);
											txtLog.Text += String.Format("\r\n\t- Relesed {0}", key.@char[0]);
										}
										else
										{
											Keyboard.ReleaseKey((Byte)key.code);
											txtLog.Text += String.Format("\r\n\t- Released KeyCode {0}", key.code);
										}
										break;
								}
								#endregion
							}
						}
					}

					CommandType currentCommand = FindCommand(e.Result.Text);
					if (currentCommand.InnerCommands != null)
					{
						SetCurrentActions(currentCommand.InnerCommands);

						if (chkSounds.Checked)
						{
							_computerSound.Play();
						}
					}
					else if (!_currentConfiguration.IsOriginal)
					{
						SetCurrentActions(_originalConfiguration);
					}
					
					#endregion
				}
				
				//Special commands are always checked, regardless of the available commands
				switch (e.Result.Text)
				{
					case CancelCommand:
						if (!_currentConfiguration.IsOriginal)
						{
							SetCurrentActions(_originalConfiguration);
						}
						txtLog.Text += "Command Canceled";
						break;
				}
			}
			else
			{
				txtLog.Text += String.Format("Command '{0}' ommited ({1}% accuracy)",
					e.Result.Text,
					accuracy);
			}

			if (txtLog.Text.Length > logLength)
			{
				txtLog.Text += "\r\n\r\n";
			}
		}

		void tbConfidence_ValueChanged(object sender, EventArgs e)
		{
			txtConfidence.Text = tbConfidence.Value.ToString();
		}

		private void frmKeySender_Load(object sender, EventArgs e)
		{
			_sre.RecognizeAsync(RecognizeMode.Multiple);
		}

		private void btnClose_Click(object sender, EventArgs e)
		{
			_sre.RecognizeAsyncStop();
			this.Close();
		}

		private void txtLog_TextChanged(object sender, EventArgs e)
		{
			if (chkEnd.Checked)
			{
				txtLog.SelectionStart = txtLog.Text.Length;
				txtLog.ScrollToCaret();
			}
		}
		#endregion
	}
}